using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;
using OpenWaves.Collections;
using OpenWaves.Pagination;

namespace OpenWaves.Web.Pagination
{
    public static class PaginationViewModel
    {
        public static PaginationViewModel<T> Empty<T>()
        {
            return new PaginationViewModel<T>(0, 0, null, null, null, null, null, Enumerable.Empty<PageViewModel<T>>());
        }

        public static PaginationViewModel<T> Create<T>(int totalItemsCount, IEnumerable<IPage<T>> pages, int currentPageIndex, int maxNumberOfPagesToShow = 5)
        {
            if (pages.IsEmpty())
                return Empty<T>();

            var pagesCount = pages.Count();
            currentPageIndex = Math.Min(currentPageIndex, pagesCount - 1);

            var pageViewModelFactory = new PageViewModelFactory<T>(pagesCount, currentPageIndex);

            var firstPage = pageViewModelFactory.Create(pages.First());
            var lastPage = pageViewModelFactory.Create(pages.Last());

            var currentPage = pageViewModelFactory.Create(pages.ElementAt(currentPageIndex));

            PageViewModel<T> previousPage = null;
            if (currentPageIndex > 0)
                previousPage = pageViewModelFactory.Create(pages.ElementAt(currentPageIndex - 1));

            PageViewModel<T> nextPage = null;
            if (currentPageIndex < pagesCount - 1)
                nextPage = pageViewModelFactory.Create(pages.ElementAt(currentPageIndex + 1));

            var firstPageToShow = Math.Max(0,
                Math.Min(currentPageIndex - maxNumberOfPagesToShow / 2,
                pagesCount - maxNumberOfPagesToShow));

            var visiblePages = pages
                .GetRange(firstPageToShow, maxNumberOfPagesToShow)
                .Select(pageViewModelFactory.Create)
                .ToList();

            return new PaginationViewModel<T>(totalItemsCount, pagesCount,
                firstPage, lastPage, currentPage, previousPage, nextPage, 
                visiblePages);            
        }

        private class PageViewModelFactory<T>
        {
            private const int firstPageIndex = 0;
            private readonly int lastPageIndex;
            private readonly int currentPageIndex;

            private readonly IDictionary<IPage<T>, PageViewModel<T>> pageViewModelsLookup = new Dictionary<IPage<T>, PageViewModel<T>>();

            public PageViewModelFactory(int pagesCount, int currentPageIndex)
            {
                this.lastPageIndex = pagesCount - 1;
                this.currentPageIndex = currentPageIndex;
            }

            public PageViewModel<T> Create(IPage<T> page)
            {
                return this.pageViewModelsLookup.GetOrCreate(
                    page,
                    this.CreateViewModel);
            }

            private PageViewModel<T> CreateViewModel(IPage<T> page)
            {
                return new PageViewModel<T>(
                    page,
                    page.Index == firstPageIndex,
                    page.Index == this.lastPageIndex,
                    page.Index == this.currentPageIndex);
            }
        }
    }

    public class PaginationViewModel<T>
    {
        private readonly int totalItemsCount;
        private readonly int totalPagesCount;
        private readonly PageViewModel<T> firstPage;
        private readonly PageViewModel<T> lastPage;
        private readonly PageViewModel<T> currentPage;
        private readonly PageViewModel<T> nextPage;
        private readonly PageViewModel<T> previousPage;

        private readonly IEnumerable<PageViewModel<T>> visiblePages;

        public PaginationViewModel(int totalItemsCount, int totalPagesCount, PageViewModel<T> firstPage, PageViewModel<T> lastPage, PageViewModel<T> currentPage, PageViewModel<T> previousPage, PageViewModel<T> nextPage, IEnumerable<PageViewModel<T>> visiblePages)
        {
            Contract.Requires<ArgumentOutOfRangeException>(totalItemsCount >=0);
            Contract.Requires<ArgumentOutOfRangeException>(totalPagesCount >= 0);
            Contract.Requires<ArgumentNullException>(visiblePages != null);

            this.totalPagesCount = totalPagesCount;
            this.firstPage = firstPage;
            this.lastPage = lastPage;
            this.currentPage = currentPage;
            this.nextPage = nextPage;
            this.previousPage = previousPage;
            this.visiblePages = visiblePages;
            this.totalItemsCount = totalItemsCount;
        }

        public PageViewModel<T> FirstPage
        {
            get { return firstPage; }
        }

        public PageViewModel<T> LastPage
        {
            get { return lastPage; }
        }

        public PageViewModel<T> CurrentPage
        {
            get { return currentPage; }
        }

        public PageViewModel<T> NextPage
        {
            get { return nextPage; }
        }

        public PageViewModel<T> PreviousPage
        {
            get { return previousPage; }
        }

        public IEnumerable<PageViewModel<T>> VisiblePages
        {
            get { return visiblePages; }
        }

        public int TotalItemsCount
        {
            get { return this.totalItemsCount; }
        }

        public int TotalPagesCount
        {
            get { return this.totalPagesCount; }
        }
    }
}